with SDL.Events;
with Ada.Containers.Hashed_Sets;
with Ada.Containers.Hashed_Maps;
package body Legend.Timers is

   function Hash (Key : Hash_Type) return Hash_Type 
   is
      pragma Inline (Hash);
   begin
      return Key;
   end Hash;

   package Timer_Sets is
      new Hashed_Sets (Hash_Type, Hash, "=");

   package Timer_Maps is
      new Hashed_Maps (
         Key_Type        => Hash_Type,
         Element_Type    => Timer_Type,
         Hash            => Hash,
         Equivalent_Keys => "=");

   task body World_Clock is
      use Ada.Calendar;
      gameover : Boolean := True;
      seed     : Hash_Type := 1;
      ts       : Timer_Maps.Map;
      expired  : Timer_Sets.Set;
      e        : SDL.Events.Event_t(ev => SDL.Events.ES_USER_EVENT);
      procedure Check_Timeup
      is
         data1_as_id : Hash_Type
         with Import, Convention => Ada, Address => e.User.Data1'Address;
         data2_as_count : Natural
         with Import, Convention => Ada, Address => e.User.Data2'Address;
      begin
         expired.Clear;
         for i in ts.Iterate loop
            declare
               id : Hash_Type := Timer_Maps.Key(i);
               t  : Timer_Maps.Reference_Type := ts.Reference(i);
            begin
               t.Remain_Interval := t.Remain_Interval - World_Clock_Interval;
               if t.Remain_Interval <= 0.0 then
                  t.Trigger_Count := t.Trigger_Count + 1;
                  data1_as_id := Id;
                  data2_as_count := t.Trigger_Count;
                  SDL.Events.Push_Event(e);
                  if t.Repeat = 0 then
                     t.Remain_Interval := t.Interval;
                  else
                     t.Remain_Repeat := t.Remain_Repeat - 1;
                     if t.Remain_Repeat = 0 then
                        expired.Include(id);
                     else
                        t.Remain_Interval := t.Interval;
                     end if;
                  end if;
               end if;
            end;
         end loop;
         for id of expired loop
            ts.Delete(id);
         end loop;
      end Check_Timeup;
   begin
      select
         accept Start  do
            gameover := False;
         end Start;
      or
         accept Stop do
            gameover := True;
         end Stop;
      end select;
      e.User.Ev_Type := SDL.Events.Is_User_Event;
      e.User.Code := User_Event_Code_Timer;
      while not gameover loop
         select
            accept Stop  do
               gameover := True;
            end Stop;
         or
            accept Add_Timer (
               Interval : Duration; 
               Repeat   : Natural; 
               At_Time  : Duration := 0.0;
               Id       : out Hash_Type
               ) do
               declare
                  t : Timer_Type := (
                     Interval        => Interval,
                     Repeat          => Repeat,
                     Remain_Interval => At_Time,
                     Remain_Repeat   => Repeat,
                     Trigger_Count   => 0);
               begin
                  ts.Include (seed, t);
                  Id := seed;
                  seed := seed + 1;
               end;
            end Add_Timer;
         or
            accept Modify_Timer (
               Id       : Hash_Type; 
               Interval : Duration; 
               Repeat   : Natural
               ) do
               declare
                  c : Timer_Maps.Cursor := ts.Find(Id);
               begin
                  if Timer_Maps.Has_Element(c) then
                     declare 
                        t : Timer_Maps.Reference_Type := ts.Reference(c);
                     begin
                        t.Interval := Interval;
                        t.Remain_Interval := Interval;
                        t.Repeat := Repeat;
                     end;
                  end if;
               end;
            end Modify_Timer;
         or
            accept Delete_Timer (Id : Hash_Type) do
               declare
                  c : Timer_Maps.Cursor := ts.Find (Id);
               begin
                  if Timer_Maps.Has_Element(c) then
                     ts.Delete(C);
                  end if;
               end;
            end Delete_Timer;
         or
            delay World_Clock_Interval;
            Check_Timeup;
         end select;
      end loop;
   end World_Clock;


end Legend.Timers;
